﻿using EventBus;
using LiteStep.Conversions;
using LiteStep.Currencies;
using LiteStep.Events;
using LiteStep.Events.Handlers;
using LiteStep.Exchanges;
using LiteStep.Exchanges.Vircurex;
using LiteStep.Fees;
using LiteStep.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LiteStep.Arbitrage
{
    public class TriangleCalculator<TOne, TTwo, TThree, TExchange> : ITriangleCalculator
        where TOne : ICurrency
        where TTwo : ICurrency
        where TThree : ICurrency
        where TExchange : IExchange
    {
        private UpdateListener<TOne, TTwo, TExchange> FirstUpdateListener { get; set; }
        private UpdateListener<TTwo, TThree, TExchange> SecondUpdateListener { get; set; }
        private UpdateListener<TThree, TOne, TExchange> ThirdUpdateListener { get; set; }

        private IEventBus EventBus { get; set; }

        public void Register(IEventBus eventBus)
        {
            if (FirstUpdateListener == null)
            {
                FirstUpdateListener = new UpdateListener<TOne, TTwo, TExchange>(CalculateArbitrage);
            }
            if (SecondUpdateListener == null)
            {
                SecondUpdateListener = new UpdateListener<TTwo, TThree, TExchange>(CalculateArbitrage);
            }
            if (ThirdUpdateListener == null)
            {
                ThirdUpdateListener = new UpdateListener<TThree, TOne, TExchange>(CalculateArbitrage);
            }

            EventBus = eventBus;
            FirstUpdateListener.Register(eventBus);
            SecondUpdateListener.Register(eventBus);
            ThirdUpdateListener.Register(eventBus);
        }

        public void Unregister()
        {
            FirstUpdateListener.Unregister();
            SecondUpdateListener.Unregister();
            ThirdUpdateListener.Unregister();
        }

        private void CalculateArbitrage()
        {
            lock (this)
            {
                var conversionManager = LiteStep.Ninjector.Get<IConversionManager>();
                var feeStructureManager = LiteStep.Ninjector.Get<IFeeStructureManager>();

                var feeStructure = feeStructureManager.Get<TExchange>() ?? new ZeroFeeStructure<TExchange>();

                var conversionOne = conversionManager.Get<TOne, TTwo, TExchange>();
                var conversionTwo = conversionManager.Get<TTwo, TThree, TExchange>();
                var conversionThree = conversionManager.Get<TThree, TOne, TExchange>();

                if (conversionOne == null || conversionTwo == null || conversionThree == null) return;

                var volumeThree = conversionThree.From.Amount;
                var volumeTwo = Math.Min(conversionTwo.From.Amount, volumeThree / conversionTwo.ConversionRatio);
                var volumeOne = Math.Min(conversionOne.From.Amount, volumeTwo / conversionOne.ConversionRatio);

                var costOfConversionOne = volumeOne * (1.0M + feeStructure.GetFee<TOne, TTwo>(volumeOne));
                var balanceOne = volumeOne * conversionOne.ConversionRatio;

                volumeTwo = Math.Min(volumeTwo, balanceOne);
                var costOfConversionTwo = volumeTwo * (1.0M + feeStructure.GetFee<TTwo, TThree>(volumeTwo));
                var balanceTwo = volumeTwo * conversionTwo.ConversionRatio;

                volumeThree = Math.Min(volumeThree, balanceTwo);
                var costOfConversionThree = volumeThree * (1.0M + feeStructure.GetFee<TThree, TTwo>(volumeThree));
                var balanceThree = volumeThree * conversionThree.ConversionRatio;

                var totalCost = costOfConversionOne * (1.0M + feeStructure.GetFee<TOne, TTwo>(volumeOne)) * (1.0M + feeStructure.GetFee<TOne, TTwo>(volumeOne));
                var percentageChange = (balanceThree / totalCost) - 1;
                var valueChange = balanceThree - totalCost;

                var key = String.Format("{0}{1}{2}TriangleCalculator", typeof(TOne).Name, typeof(TTwo).Name, typeof(TThree).Name);

                var arbitrageCalculation = new ArbitrageCalculation(key, percentageChange, valueChange, 0.0M);

                EventBus.FireEvent(new ArbitrageCalculationUpdatedEvent(arbitrageCalculation));
            }
        }

        private class UpdateListener<TFrom, TTo, TOn> : IConversionUpdatedEventHandler<TFrom, TTo, TOn>
            where TFrom : ICurrency
            where TTo : ICurrency
            where TOn : IExchange
        {
            private IHandlerRegistration registration { get; set; }
            private Action callback { get; set; }

            public UpdateListener(Action callback)
            {
                this.callback = callback;
            }

            public void Register(IEventBus eventBus)
            {
                registration = eventBus.AddHandler(ConversionUpdatedEvent<TFrom, TTo, TOn>.TYPE, this);
            }

            public void Unregister()
            {
                registration.RemoveHandler();
            }

            public void OnConversionUpdatedEvent(ConversionUpdatedEvent<TFrom, TTo, TOn> e)
            {
                callback.Invoke();
            }
        }

    }
}
